import { User } from "@/entities/User";
import { App } from "@/entities/App";
import { AppUser } from "@/entities/AppUser";
import { initORM } from "@/utils";
import { NextApiRequest, NextApiResponse } from "next";
import { getAllUsersSimply, getCurrentUser } from "./user";
import { getRandPassword, pickData, sleep } from "@/utils/base";
import { createMd5 } from "@/utils/hash";

export async function findAppUserRecord(appId: number, userId: number) {
  if (!appId || Array.isArray(appId)) {
    return {
      success: false,
      data: {},
      message: "Missing app id or app id is array",
    };
  }

  if (!userId) {
    return {
      success: false,
      data: {},
      message: "Missing user id",
    };
  }

  const em = await initORM();
  const appRef = em.getReference(App, Number(appId));
  const userRef = em.getReference(User, userId);
  const appUserRecord = await em.findOne(AppUser, {
    app: appRef,
    user: userRef,
  });

  if (!appUserRecord) {
    return {
      success: false,
      errCode: "USER_NOT_IN_APP",
      data: {},
      message: "App user record not found",
    };
  }

  return {
    success: true,
    data: appUserRecord,
    message: "Query app user record success",
  };
}

export async function getApp(req: NextApiRequest) {
  const curUser = await getCurrentUser(req);

  if (!curUser) {
    return {
      success: false,
      errCode: "USER_NOT_FOUND",
      data: {},
      message: "User not found",
    };
  }

  const appId = req.query.appid || req.body?.appid;

  const appUserResult = await findAppUserRecord(Number(appId), curUser.id);
  if (!appUserResult.success) {
    return appUserResult;
  }

  const appUserRecord = appUserResult.data as AppUser;

  const isSuperUser = curUser.role === 1;

  // admin user can access all apps
  if (!appUserRecord && !isSuperUser) {
    return {
      success: false,
      errCode: "USER_NO_PERMISSION",
      data: {},
      message: "User has no permission for this app",
    };
  }

  const em = await initORM();
  const app = await em.findOne(App, Number(appId));

  if (!app) {
    return {
      success: false,
      errCode: "APP_NOT_FOUND",
      data: {},
      message: "App not found",
    };
  }

  return {
    success: true,
    data: app,
    message: "Query app success",
  };
}

export async function updateApp(
  req: NextApiRequest,
  res: NextApiResponse<APIRes>
) {
  const { id, name, isDelete, introduce, type } = req.body ?? {};

  if (!id) {
    res.status(400).json({
      success: false,
      data: {},
      message: "Missing app id",
    });
    return;
  }

  req.query.appid = id;

  const appResult = await getApp(req);

  if (!appResult.success) {
    res.status(400).json(appResult as APIRes);
    return;
  }

  const app = appResult.data as App;

  const keys = ["name", "isDelete", "introduce", "type"] as const;

  keys.forEach((key) => {
    if (req.body[key] !== undefined) {
      Object.assign(app, { [key]: req.body[key] as string });
    }
  });

  const em = await initORM();
  await em.persistAndFlush(app);

  res.status(200).json({
    success: true,
    data: app,
    message: "Update app success",
  });
}

export async function getApps(
  req: NextApiRequest,
  res: NextApiResponse<APIRes>
) {
  const em = await initORM();

  const curUser = await getCurrentUser(req);

  const isSuperUser = curUser?.role === 1;
  const appListOfCurUser = await getAppListOfCurrentUser(req);
  // console.log("🚀 ~ appListOfCurUser:", appListOfCurUser);
  const appListOfAllUser = await getAppListOfAnyUser(req);

  //   group by app id
  type ResApp = App & { users: Array<{ id: number; name: string }> };
  const allApps = [] as Array<ResApp>;
  appListOfAllUser.forEach((appUser) => {
    const app = appUser.app;
    // @ts-ignore
    const matchedApp = allApps.find((row) => row.id === app.id);
    if (!matchedApp) {
      // @ts-ignore
      const resAppItem = Object.assign({}, app, {
        // @ts-ignore
        users: [
          {
            // @ts-ignore
            id: appUser.user.id,
            // @ts-ignore
            name: appUser.user.name,
            // @ts-ignore
            avatarData: appUser.user.avatarData,
          },
        ],
      }) as unknown as ResApp;
      allApps.push(resAppItem);
    } else {
      const matchedUsers = matchedApp.users;
      // @ts-ignore
      if (!matchedUsers.find((row) => row.id === appUser.user.id)) {
        // @ts-ignore
        matchedUsers.push({
          // @ts-ignore
          id: appUser.user.id,
          // @ts-ignore
          name: appUser.user.name,
          // @ts-ignore
          avatarData: appUser.user.avatarData,
        });
      }
    }
  });

  const apps = allApps.filter((app) => {
    // @ts-ignore
    return appListOfCurUser.find((appUser) => appUser.app.id === app.id);
  });

  res.status(200).json({
    success: true,
    data: apps,
    message: "Query apps success",
  });
}

export async function createApp(
  req: NextApiRequest,
  res: NextApiResponse<APIRes>
) {
  const em = await initORM();
  const curUser = await getCurrentUser(req);
  if (!curUser) {
    res.status(400).json({
      success: false,
      data: {},
      message: "User not found",
    });
    return;
  }

  const secret = createMd5(getRandPassword(6));
  const app = await em.create(
    App,
    Object.assign({ user: curUser, secret }, req.body)
  );
  await em.persistAndFlush(app);
  await sleep(1);
  const result = await createAppUserRecord(app, curUser);

  res.status(200).json({
    success: true,
    data: pickData(app, ["id", "name"]),
    message: "App created",
  });
}

export async function upsertAppUserRecords(
  appId: number,
  userIds: Array<number>,
  isDelete = 0
) {
  const em = await initORM();
  const appRef = em.getReference(App, appId);
  const userRefList = userIds.map((id) => em.getReference(User, id));

  const appUserRecords = [] as Array<AppUser>;
  for (const userRef of userRefList) {
    const recordResult = await findAppUserRecord(appId, userRef.id);
    if (!recordResult.success) {
      // do not exists
      const appUser = em.create(AppUser, {
        app: appRef,
        user: userRef,
        isDelete,
      });

      await em.persistAndFlush(appUser);
      appUserRecords.push(appUser);
    } else {
      // exists
      const appUser = recordResult.data as AppUser;
      appUser.isDelete = isDelete;

      await em.persistAndFlush(appUser);
      appUserRecords.push(appUser);
    }
  }

  return {
    success: true,
    data: appUserRecords,
    message: "Upsert app user records success",
  };
}

export async function createAppUserRecord(app: App, user: User) {
  const em = await initORM();
  const appUser = await em.create(AppUser, {
    app,
    user,
    isDelete: 0,
  });

  await em.persistAndFlush(appUser);

  return appUser;
}

export async function getAppListOfCurrentUser(req: NextApiRequest) {
  const em = await initORM();

  const curUser = await getCurrentUser(req);

  if (!curUser) {
    return [];
  }

  const isSuperUser = curUser.role === 1;

  const appList = await em.find(
    AppUser,
    isSuperUser
      ? { isDelete: 0 }
      : {
          user: curUser,
          isDelete: 0, // only users that still belong to this app
        },
    {
      populate: ["app", "user"],
    }
  );
  //   console.log("🚀 ~ appList:", appList);

  // return appList.map(row => pickData(row.app, ['id', 'name']));

  // @ts-ignore
  //   return appList.map((row) => row.app.id);

  return appList;
}

export async function getAppListOfAnyUser(req: NextApiRequest) {
  const em = await initORM();

  const appList = await em.find(
    AppUser,

    { isDelete: 0 },
    {
      populate: ["app", "user"],
    }
  );

  // console.log("========getAppListOfAnyUser===========>>>appList::", appList);
  return appList;
}

export async function getUserListOfApp(req: NextApiRequest) {
  const appId = req.query.appid || req.body?.appid;
  if (!appId || Array.isArray(appId)) {
    return {
      success: false,
      data: {},
      message: "Missing app id or app id is array",
    };
  }
  const em = await initORM();
  const appRef = em.getReference(App, Number(appId));
  const appUserList = await em.find(AppUser, {
    app: appRef,
    isDelete: 0,
  });

  if (!appUserList.length) {
    return {
      success: false,
      data: {},
      message: "Users of this app not found",
    };
  }

  const users = appUserList.map((row) => row.user);
  // return appUserList
  return {
    success: true,
    data: { users },
    message: "Query app users success",
  };
}

export async function getGuestUsersOfApp(req: NextApiRequest) {
  const appUsersResult = await getUserListOfApp(req);
  if (!appUsersResult.success) {
    return appUsersResult;
  }

  const appUsers = appUsersResult.data.users as unknown as Array<User>;

  const allUsersResult = await getAllUsersSimply(req);

  if (!allUsersResult.success) {
    return allUsersResult;
  }

  const allUsers = allUsersResult.data as Array<User>;

  const guests = allUsers.filter(
    (user) => !appUsers.find((row) => row.id === user.id)
  );

  return {
    success: true,
    data: { guests },
    message: "Query guest users success",
  };
}

export async function getCurrentUserRole(req: NextApiRequest) {
  const curUser = await getCurrentUser(req);

  if (!curUser) {
    return {
      success: false,
      data: {},
      message: "User not found",
    };
  }

  const isSuperUser = curUser.role === 1;
  let isAdminUser = false;

  const hasAppId = req.query.appid || req.body?.appid;
  if (hasAppId) {
    const appResult = await getApp(req);

    if (appResult.success) {
      const app = appResult.data as App;
      // @ts-ignore
      isAdminUser = app.admin.id === curUser.id;
    } else {
      if (appResult.errCode === "USER_NOT_IN_APP") {
        isAdminUser = false;
      } else {
        return appResult;
      }
    }
  }

  const role = {
    superUser: isSuperUser,
    adminUser: isAdminUser,
    normalUser: !isSuperUser && !isAdminUser,
  };

  return {
    success: true,
    data: role,
    message: "Query current user role success",
  };
}

export async function checkAdminAuth(
  req: NextApiRequest,
  res: NextApiResponse
) {
  const roleResult = await getCurrentUserRole(req);
  if (!roleResult.success) {
    res.status(400).json(roleResult);
    return false;
  }

  const role = roleResult.data as {
    superUser: boolean;
    adminUser: boolean;
    normalUser: boolean;
  };

  if (!role.superUser && !role.adminUser) {
    res.status(400).json({
      success: false,
      data: {},
      message: "You are not admin user",
    });

    return false;
  }

  return true;
}

export async function addAppUsers(req: NextApiRequest, res: NextApiResponse) {
  const appId = req.query.appid || req.body?.appid;
  const userIds = req.body?.userIds || req.query?.userIds;

  if (!appId || Array.isArray(appId)) {
    return res.status(400).json({
      success: false,
      data: {},
      message: "Missing app id or app id is array",
    });
  }

  if (!userIds) {
    return res.status(400).json({
      success: false,
      data: {},
      message: "Missing user ids",
    });
  }

  const roleResult = await getCurrentUserRole(req);
  if (!roleResult.success) {
    return res.status(400).json(roleResult);
  }

  const role = roleResult.data as {
    superUser: boolean;
    adminUser: boolean;
    normalUser: boolean;
  };

  if (!role.superUser && !role.adminUser) {
    return res.status(400).json({
      success: false,
      data: {},
      message: "You are not admin user",
    });
  }

  const upsertResult = await upsertAppUserRecords(appId, userIds, 0);

  res.status(200).json(upsertResult);
  return;
}
